#region License

/* Copyright (C) 2007 Cristian Libardo
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 */

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using FlexiCommerce.Components;
using FlexiCommerce.Details;
using NHibernate.Criterion;
using NHibernate;
using FlexiCommerce.Web.UI.DynamicData;
using System.Security.Cryptography;

namespace FlexiCommerce.Persistence.NH
{
	/// <summary>
	/// A wrapper for NHibernate persistence functionality.
	/// </summary>
	public class ContentPersister : IPersister
	{
		private readonly IRepository<int, GenericPersistenceItem> itemRepository;
        private readonly IRepository<int, Category> categoryRepository;
        private readonly IRepository<int, CategoryDetails> categoryDetailsRepository;
		private readonly INHRepository<int, LinkDetail> linkRepository;
        private readonly IRepository<int, Language> languageRepository;
        private readonly IRepository<int, Product> productRepository;
        private readonly IRepository<int, ProductDetails> productDetailsRepository;
        private readonly IRepository<int, Image> imageRepository;
        private readonly IRepository<int, Components.Configuration> configurationRepository;

		/// <summary>Creates a new instance of the DefaultPersistenceManager.</summary>
		public ContentPersister(IRepository<int, GenericPersistenceItem> itemRepository,IRepository<int, Category> categoryRepository, INHRepository<int, LinkDetail> linkRepository,
		                        IRepository<int, CategoryDetails> categoryDetailsRepository, IRepository<int, Language> languageRepository,
            IRepository<int,Product> productRepository, IRepository<int,ProductDetails> productDetailsRepository,
            IRepository<int,Image> imageRepository,
            IRepository<int, Components.Configuration> configurationRepository)
		{
            this.languageRepository = languageRepository;
			this.itemRepository = itemRepository;
			this.linkRepository = linkRepository;
            this.categoryRepository = categoryRepository;
            this.categoryDetailsRepository = categoryDetailsRepository;
            this.productDetailsRepository = productDetailsRepository;
            this.productRepository = productRepository;
            this.imageRepository = imageRepository;
            this.configurationRepository = configurationRepository;
		}

		#region Load, Save, & Delete Methods

		/// <summary>Gets an item by id</summary>
		/// <param name="id">The id of the item to load</param>
		/// <returns>The item if one with a matching id was found, otherwise null.</returns>
		public virtual GenericPersistenceItem Get(int id)
		{
            GenericPersistenceItem item = itemRepository.Get(id);
            if (ItemLoaded != null)
            {
                return (GenericPersistenceItem)Invoke(ItemLoaded, new ItemEventArgs(item)).AffectedItem; 
            }
            return item;
		}

		/// <summary>Gets an item by id</summary>
		/// <typeparam name="T">The type of object that is expected</typeparam>
		/// <param name="id">The id of the item to load</param>
		/// <returns>The item if one with a matching id was found, otherwise null.</returns>
		public virtual T Get<T>(int id) where T : PersistenceItem
		{
            //if(typeof(T) == typeof(GenericPersistenceItem))
            //    return (T)(PersistenceItem)Get(id);
            //return null;
            if (typeof(GenericPersistenceItem).IsAssignableFrom(typeof(T)))
            {
                return (T)(PersistenceItem)itemRepository.Get(id);
            }
            else if (typeof(T) == typeof(Category))
            {
                return categoryRepository.Get(id) as T;
            }
            else if (typeof(T) == typeof(CategoryDetails))
            {
                return categoryDetailsRepository.Get(id) as T;
            }
            else if (typeof(T) == typeof(Language))
            {
                return languageRepository.Get(id) as T;
            }
            else if (typeof(T) == typeof(Product))
            {
                return productRepository.Get(id) as T;
            }
            else if (typeof(T) == typeof(ProductDetails))
            {
                return productDetailsRepository.Get(id) as T;
            }
            else if (typeof(T) == typeof(Image))
            {
                return imageRepository.Get(id) as T;
            }
            else if (typeof(T) == typeof(Components.Configuration))
            {
                return imageRepository.Get(id) as T;
            }
            else
            {
                throw new FlexiException("{0} does not have a repository", typeof(T).Name);
            }

		}

		/// <summary>Saves or updates an item storing it in database</summary>
		/// <param name="unsavedItem">Item to save</param>
		public virtual void Save(PersistenceItem unsavedItem)
		{
		    Utility.InvokeEvent(ItemSaving, unsavedItem, this, SaveAction);
		}

		private void SaveAction(PersistenceItem item)
		{
			if (item is IActiveContent)
			{
				(item as IActiveContent).Save();
			}
			else
			{
                using (ITransaction transaction = RGetTransaction(item.GetType()))
				{

				    if(item is IPersistenceItemLife)
				    {
                        (item as IPersistenceItemLife).Updated = Utility.CurrentTime();
				        (item as IPersistenceItemLife).SavedBy = FlexiCommerce.Context.Current.RequestContext.User.Identity.Name;
				    }

                    if(item is HierarchicalPersistenceItem)
                    {
                        (item as HierarchicalPersistenceItem).AddTo((item as HierarchicalPersistenceItem).Parent);
                        EnsureSortOrder(item as HierarchicalPersistenceItem);
                    }

					
                    RSaveOrUpdate(item);

					transaction.Commit();
				}
			}
			Invoke(ItemSaved, new ItemEventArgs(item));
		}

        private ITransaction RGetTransaction(Type type)
        {
            if (typeof(GenericPersistenceItem).IsAssignableFrom(type))
            {
                return itemRepository.BeginTransaction();
            }
            else if (type == typeof(Category))
            {
                return categoryRepository.BeginTransaction();
            }
            else if (type == typeof(CategoryDetails))
            {
                return categoryDetailsRepository.BeginTransaction();
            }
            else if (type == typeof(Language))
            {
                return languageRepository.BeginTransaction();
            }
            else if (type == typeof(Product))
            {
                return productRepository.BeginTransaction();
            }
            else if (type == typeof(ProductDetails))
            {
                return productDetailsRepository.BeginTransaction();
            }
            else if (type == typeof(Image))
            {
                return imageRepository.BeginTransaction();

            }
            else if (type == typeof(Components.Configuration))
            {
                return configurationRepository.BeginTransaction();
            }
            else
            {
                throw new FlexiException("{0} does not have a repository", type.Name);
            }
        }

		private void EnsureSortOrder(HierarchicalPersistenceItem unsavedItem)
		{
			if (unsavedItem.Parent != null)
			{
                IEnumerable<HierarchicalPersistenceItem> updatedItems = Utility.UpdateSortOrder(unsavedItem.Parent.Children);
                foreach (HierarchicalPersistenceItem updatedItem in updatedItems)
				{
				    RSaveOrUpdate(updatedItem as PersistenceItem);
				}
			}
		}

		/// <summary>Deletes an item an all sub-items</summary>
		/// <param name="itemNoMore">The item to delete</param>
		public void Delete(PersistenceItem itemNoMore)
		{
			Utility.InvokeEvent(ItemDeleting, itemNoMore, this, DeleteAction);
		}

		void DeleteAction(PersistenceItem itemNoMore)
		{
			Trace.TraceInformation("NHPersistenceManager.Delete " + itemNoMore);

			if (itemNoMore is IActiveContent)
			{
				(itemNoMore as IActiveContent).Delete();
			}
			else
			{
				using (ITransaction transaction = itemRepository.BeginTransaction())
				{
					DeleteRecursive(itemNoMore);

					transaction.Commit();
				}
			}
			Invoke(ItemDeleted, new ItemEventArgs(itemNoMore));
		}

		#region Delete Helper Methods

		private void DeleteRecursive(PersistenceItem itemNoMore)
		{

		    HierarchicalPersistenceItem hitem = itemNoMore as HierarchicalPersistenceItem;
            if(hitem != null)
            {
                List<HierarchicalPersistenceItem> children = new List<HierarchicalPersistenceItem>(hitem.Children);
			foreach(GenericPersistenceItem child in children)
                DeleteRecursive(child);
                hitem.AddTo(null);
            }

		    GenericPersistenceItem gitem = itemNoMore as GenericPersistenceItem;

            if(gitem != null)
            {
                DeleteInboundLinks((GenericPersistenceItem)itemNoMore);
            }
			

			

			RDelete(itemNoMore);
		}	

		private void DeleteInboundLinks(GenericPersistenceItem itemNoMore)
		{
			foreach (LinkDetail detail in linkRepository.FindAll(Expression.Eq("LinkedItem", itemNoMore)))
			{
				if (detail.EnclosingCollection != null)
					detail.EnclosingCollection.Remove(detail);
				detail.EnclosingItem.Details.Remove(detail.Name);
				linkRepository.Delete(detail);
			}
		}

		#endregion

		#endregion

		#region Move & Copy Methods

		/// <summary>Move an item to a destination</summary>
		/// <param name="source">The item to move</param>
		/// <param name="destination">The destination below which to place the item</param>
        public virtual void Move(HierarchicalPersistenceItem source, HierarchicalPersistenceItem destination)
		{
			Utility.InvokeEvent(ItemMoving, this, source, destination, MoveAction);
		}

        private HierarchicalPersistenceItem MoveAction(HierarchicalPersistenceItem source, HierarchicalPersistenceItem destination)
		{
			if (source is IActiveContent)
			{
				(source as IActiveContent).MoveTo(destination as PersistenceItem);
			}
			else
			{
				using (ITransaction transaction = RGetTransaction(source.GetType()))
				{
					source.AddTo(destination);
				    RSave(source as PersistenceItem);
					transaction.Commit();
				}
			}
			Invoke(ItemMoved, new DestinationEventArgs(source, destination));
			return null;
		}


		#endregion

		#region IPersistenceEventSource

		/// <summary>Occurs before an item is saved</summary>
		public event EventHandler<CancellableItemEventArgs> ItemSaving;

		/// <summary>Occurs when an item has been saved</summary>
		public event EventHandler<ItemEventArgs> ItemSaved;

		/// <summary>Occurs before an item is deleted</summary>
		public event EventHandler<CancellableItemEventArgs> ItemDeleting;

		/// <summary>Occurs when an item has been deleted</summary>
		public event EventHandler<ItemEventArgs> ItemDeleted;

		/// <summary>Occurs before an item is moved</summary>
		public event EventHandler<CancellableDestinationEventArgs> ItemMoving;

		/// <summary>Occurs when an item has been moved</summary>
		public event EventHandler<DestinationEventArgs> ItemMoved;

		/// <summary>Occurs before an item is copied</summary>
		public event EventHandler<CancellableDestinationEventArgs> ItemCopying;

		/// <summary>Occurs when an item has been copied</summary>
		public event EventHandler<DestinationEventArgs> ItemCopied;

		/// <summary>Occurs when an item is loaded</summary>
		public event EventHandler<ItemEventArgs> ItemLoaded;

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			itemRepository.Dispose();
		}

		#endregion

        /// <summary>Persists changes.</summary>
        public void Flush()
        {
            itemRepository.Flush();
            languageRepository.Flush();
            linkRepository.Flush();
            categoryRepository.Flush();
            categoryDetailsRepository.Flush();
            productDetailsRepository.Flush();
            productRepository.Flush();
        }
        public IRepository<int, GenericPersistenceItem> Repository
        {
            get { return this.itemRepository; }
        }

        private void RSaveOrUpdate(PersistenceItem item)
        {
            if (typeof(GenericPersistenceItem).IsAssignableFrom(item.GetType()))
            {
                itemRepository.SaveOrUpdate((GenericPersistenceItem)item);
            }
            else if (item.GetType() == typeof(Category))
            {
                categoryRepository.SaveOrUpdate(item as Category);
            }
            else if(item.GetType() == typeof(CategoryDetails))
            {
                categoryDetailsRepository.SaveOrUpdate(item as CategoryDetails);
            }
            else if (item.GetType() == typeof(Language))
            {
                languageRepository.SaveOrUpdate(item as Language);
            }
            else if (item.GetType() == typeof(Product))
            {
                productRepository.SaveOrUpdate(item as Product);
            }
            else if (item.GetType() == typeof(ProductDetails))
            {
                productDetailsRepository.SaveOrUpdate(item as ProductDetails);
            }
            else if (item.GetType() == typeof(Image))
            {
                imageRepository.SaveOrUpdate(item as Image);
            }
            else if (item.GetType() == typeof(Components.Configuration))
            {
                configurationRepository.SaveOrUpdate(item as Components.Configuration);
            }
            else
            {
                throw new FlexiException("{0} does not have a repository", item.GetType().Name);
            }
            
        }
        private void RDelete(PersistenceItem item)
        {
            if(item.GetType() == typeof(GenericPersistenceItem))
            {
                itemRepository.Delete(item as GenericPersistenceItem);
            }
            else if (item.GetType() == typeof(Category))
            {
                categoryRepository.Delete(item as Category);
            }
            else if(item.GetType() == typeof(CategoryDetails))
            {
                categoryDetailsRepository.Delete(item as CategoryDetails);
            }
            else if (item.GetType() == typeof(Language))
            {
                languageRepository.Delete(item as Language);
            }
            else if (item.GetType() == typeof(Product))
            {
                productRepository.Delete(item as Product);
            }
            else if (item.GetType() == typeof(ProductDetails))
            {
                productDetailsRepository.Delete(item as ProductDetails);
            }
            else if (item.GetType() == typeof(Image))
            {
                imageRepository.Delete(item as Image);
            }
            else if (item.GetType() == typeof(Components.Configuration))
            {
                configurationRepository.Delete(item as Components.Configuration);
            }
            else
            {
                throw new FlexiException("{0} does not have a repository", item.GetType().Name);
            }
        }
        private void RSave(PersistenceItem item)
        {
            if (typeof(GenericPersistenceItem).IsAssignableFrom(item.GetType()))
            {
                itemRepository.Save(item as GenericPersistenceItem);
            }
            else if (item.GetType() == typeof(Category))
            {
                categoryRepository.Save(item as Category);
            }
            else if(item.GetType() == typeof(CategoryDetails))
            {
                categoryDetailsRepository.Save(item as CategoryDetails);
            }
            else if (item.GetType() == typeof(Language))
            {
                languageRepository.Save(item as Language);
            }
            else if (item.GetType() == typeof(Product))
            {
                productRepository.Save(item as Product);
            }
            else if (item.GetType() == typeof(ProductDetails))
            {
                productDetailsRepository.Save(item as ProductDetails);
            }
            else if (item.GetType() == typeof(Image))
            {
                imageRepository.Save(item as Image);
            }
            else if (item.GetType() == typeof(Components.Configuration))
            {
                configurationRepository.Save(item as Components.Configuration);
            }
            else
            {
                throw new FlexiException("{0} does not have a repository", item.GetType().Name);
            }
        }
        protected virtual T Invoke<T>(EventHandler<T> handler, T args)
            where T : ItemEventArgs
        {
            if (handler != null)
                handler.Invoke(this, args);
            return args;
        }

        #region IPersister Members


        public System.Web.DynamicData.ModelProviders.DataModelProvider DataModel
        {
            get { return new FlexiModelProvider(); }
        }

        public System.ComponentModel.TypeDescriptionProvider TypeDescriptor(Type type)
        {
            return new FlexiCommerce.Persistence.FlexiTypeDescriptionProvider(type);
        }


        #endregion
        public Image SaveImage(System.IO.Stream imageStream)
        {
           string hash = GetFileMD5(imageStream);
           Components.Image image = Find.Images.Where(i => i.Hash == hash).SingleOrDefault();
                //Find.Items.Where.Type.Eq(typeof(Components.Image)).And.Detail("MD5").Eq<string>(hash).Select();

           if (image == null)
           {
               image = new Image();
               image.Bitmap = System.Drawing.Image.FromStream(imageStream);
               image.Hash = hash;
               Context.Persister.Save(image);
           }
           return image;

        }
        private string GetFileMD5(System.IO.Stream stream)
        {
            MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
            Byte[] byteHash = MD5.ComputeHash(stream);
            return Convert.ToBase64String(byteHash);
        }



        public FlexiCommerce.Components.Configuration GetConfiguration()
        {
            long count = configurationRepository.Count();
            Components.Configuration config = null;
            if (count > 1)
            {
                throw new FlexiException("There must be only one configuration row in the table");
            }
            if (count == 0)
            {
                config = new Components.Configuration();
                Context.Persister.Save(config);
            }
            config = Find.Configurations.First(); 
           
            return config;
        }

  
    }
}
